Skip to content

ext/bcmath: use memcmp for compare #18871

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

SakiTakamachi
Copy link
Member

Since the version using memcmp was faster than the one using BC_VECTOR, I will go with memcmp.

Benchmark

Small value

for ($i = 0; $i < 10000000; $i++) {
    bccomp('1.2345', '1.23456', 5);
}
Benchmark 1: /php-dev2/sapi/cli/php /mount/bc/cmp/0.php
  Time (mean ± σ):     438.3 ms ±   5.8 ms    [User: 432.8 ms, System: 3.1 ms]
  Range (min … max):   433.1 ms … 453.0 ms    10 runs
 
Benchmark 2: /master/sapi/cli/php /mount/bc/cmp/0.php
  Time (mean ± σ):     444.9 ms ±   6.9 ms    [User: 437.3 ms, System: 5.1 ms]
  Range (min … max):   437.1 ms … 458.1 ms    10 runs
 
Summary
  '/php-dev2/sapi/cli/php /mount/bc/cmp/0.php' ran
    1.02 ± 0.02 times faster than '/master/sapi/cli/php /mount/bc/cmp/0.php'

Middle value 1

for ($i = 0; $i < 10000000; $i++) {
    bccomp('1.234567890123', '1.234567890123', 15);
}
Benchmark 1: /php-dev2/sapi/cli/php /mount/bc/cmp/1.php
  Time (mean ± σ):     465.3 ms ±   2.9 ms    [User: 458.9 ms, System: 3.9 ms]
  Range (min … max):   459.9 ms … 470.3 ms    10 runs
 
Benchmark 2: /master/sapi/cli/php /mount/bc/cmp/1.php
  Time (mean ± σ):     489.6 ms ±   3.2 ms    [User: 483.2 ms, System: 4.1 ms]
  Range (min … max):   484.9 ms … 494.5 ms    10 runs
 
Summary
  '/php-dev2/sapi/cli/php /mount/bc/cmp/1.php' ran
    1.05 ± 0.01 times faster than '/master/sapi/cli/php /mount/bc/cmp/1.php'

Middle value 2

for ($i = 0; $i < 10000000; $i++) {
    bccomp('1.23456789012345678901234567890123456789012345678901234567890123456789', '1.23456789012345678901234567890123456789012345678901234567890123456789', 40);
}
Benchmark 1: /php-dev2/sapi/cli/php /mount/bc/cmp/2.php
  Time (mean ± σ):     484.1 ms ±   2.3 ms    [User: 478.2 ms, System: 3.6 ms]
  Range (min … max):   482.0 ms … 488.8 ms    10 runs
 
Benchmark 2: /master/sapi/cli/php /mount/bc/cmp/2.php
  Time (mean ± σ):     589.0 ms ±   3.2 ms    [User: 582.7 ms, System: 3.7 ms]
  Range (min … max):   585.1 ms … 594.4 ms    10 runs
 
Summary
  '/php-dev2/sapi/cli/php /mount/bc/cmp/2.php' ran
    1.22 ± 0.01 times faster than '/master/sapi/cli/php /mount/bc/cmp/2.php'

Large value

for ($i = 0; $i < 100000; $i++) {
    bccomp(str_repeat('1234567890', 3000), str_repeat('1234567890', 3000), 0);
}
Benchmark 1: /php-dev2/sapi/cli/php /mount/bc/cmp/3.php
  Time (mean ± σ):     480.3 ms ±   1.1 ms    [User: 474.4 ms, System: 3.5 ms]
  Range (min … max):   479.1 ms … 482.6 ms    10 runs
 
Benchmark 2: /master/sapi/cli/php /mount/bc/cmp/3.php
  Time (mean ± σ):      1.330 s ±  0.014 s    [User: 1.324 s, System: 0.003 s]
  Range (min … max):    1.319 s …  1.362 s    10 runs
 
Summary
  '/php-dev2/sapi/cli/php /mount/bc/cmp/3.php' ran
    2.77 ± 0.03 times faster than '/master/sapi/cli/php /mount/bc/cmp/3.php'

@SakiTakamachi SakiTakamachi marked this pull request as ready for review June 18, 2025 04:22
Copy link
Member

@nielsdos nielsdos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks right.
I'm always a bit concerned however with these kind of changes. It might look great in a microbenchmark, but you're replacing a tight small loop with a call to the standard library function that is much much larger in code size. This can cause instruction cache misses. You don't see this in a microbenchmak because all the code is in the cache.
memcmp may be commonly called by different parts of PHP so perhaps this is still fine though.

@SakiTakamachi
Copy link
Member Author

@nielsdos

Thank you. That concern is definitely valid.

However, since bc_compare() is used extensively within BCMath, for operations like bc_add(), bc_sub(), and bc_divide(), it tends to be called multiple times in most scenarios. This makes it more likely to remain in the instruction cache, so the actual impact may be low in practice.
(As you mentioned, its frequent use elsewhere in PHP also contributes to this.)

Therefore, I’m planning to go ahead with using memcmp() in this case.

@SakiTakamachi
Copy link
Member Author

Ah, wait a moment.
I just realized that in the case of comparing single-digit numbers, this change actually makes it slower...
I'll try measuring again with the BC_VECTOR version, and if that one turns out to have a better balance, I might consider reopening that PR instead.

@SakiTakamachi
Copy link
Member Author

I measured the instruction cache miss rate using a simple medium-scale script with valgrind.
On my ARM environment, the memcmp() version performed slightly better than the original in terms of I-cache behavior,
but the BC_VECTOR version showed a dramatic improvement, with the I1 miss rate dropping from 0.66% to 0.02%,
indicating much more stable performance under the current test conditions.
(Of course, this is only a rough guideline, actual execution time is ultimately more important.)

Since the BC_VECTOR version also showed better balance in execution time, I will reopen the PR for that version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants